# state.rb - State holder class
# Copyright (C) 2005-2009 Akira TAGOH

# Authors:
#   Akira TAGOH  <akira@tagoh.org>

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

require 'thread'
require 'prune/debug'
require 'prune/ciarray'
require 'prune/cihash'
require 'prune/error'
require 'prune/pattern'
require 'prune/types'


module PRUNE

=begin rdoc

== PRUNE::NickList

=end

  class NickList
    include PRUNE::Debug

=begin rdoc

=== PRUNE::NickList#new

=end

    def initialize
      @nicklist = PRUNE::CiHash.new
    end # def initialize

=begin rdoc

=== PRUNE::NickList#add(val)

=end

    def add(val)
      unless val.kind_of?(PRUNE::TYPE::NickInfoStruct) then
        bug("Found an unexpected object in `%s': %s", self, val)
        nil
      else
        if @nicklist.has_key?(val.nick) then
          bug("`%s' was already in the list", val.nick)
          nil
        else
          @nicklist[val.nick] = val
        end
      end
    end # def add

=begin rdoc

=== PRUNE::NickList#delete(nick)

=end

    def delete(nick)
      unless @nicklist.has_key?(nick) then
        bug("No such nick name is in %s#delete: %s", self, nick)
      else
        @nicklist.delete(nick)
      end
    end # def delete

=begin rdoc

=== PRUNE::NickList#include?(nick)

=end

    def include?(nick)
      @nicklist.has_key?(nick)
    end # def include?

=begin rdoc

=== PRUNE::NickList#size

=end

    def size
      @nicklist.size
    end # def size

    alias :length :size

=begin rdoc

=== PRUNE::NickList#nicks

=end

    def nicks
      @nicklist.origkeys
    end # def nicks

=begin rdoc

=== PRUNE::NickList#has_oper?(nick)

=end

    def has_oper?(nick)
      (@nicklist[nick].oper == true rescue false)
    end # def has_oper?

=begin rdoc

=== PRUNE::NickList#set_oper(nick, flag)

=end

    def set_oper(nick, flag)
      unless @nicklist.has_key?(nick) then
        bug("No such nick name is in %s#set_oper: %s", self, nick)
      else
        @nicklist[nick].oper = (flag == true)
      end
    end # def set_oper

=begin rdoc

=== PRUNE::NickList#clear_oper

=end

    def clear_oper
      @nicklist.each do |k, v|
        v.oper = false
      end
    end # def clear_oper

=begin rdoc

=== PRUNE::NickList#opers

=end

    def opers
      nicks.reject {|x| !has_oper?(x)}
    end # def opers

=begin rdoc

=== PRUNE::NickList#has_voice?(nick)

=end

    def has_voice?(nick)
      (@nicklist[nick].voice == true rescue false)
    end # def has_voice?

=begin rdoc

=== PRUNE::NickList#set_voice(nick, flag)

=end

    def set_voice(nick, flag)
      unless @nicklist.has_key?(nick) then
        bug("No such nick name is in %s#set_voice: %s", self, nick)
      else
        @nicklist[nick].voice = (flag == true)
      end
    end # def set_voice

=begin rdoc

=== PRUNE::NickList#clear_voice

=end

    def clear_voice
      @nicklist.each do |k, v|
        v.voice = false
      end
    end # def clear_voice

=begin rdoc

=== PRUNE::NickList#voices

=end

    def voices
      nicks.reject {|x| !has_voice?(x)}
    end # def voices

=begin rdoc

=== PRUNE::NickList#change_nick(old, new)

=end

    def change_nick(old, new)
      if !@nicklist.has_key?(old) then
        bug("No such nick name in %s: %s", self, old)
        nil
      elsif @nicklist.has_key?(new) then
        warning("The nick name `%s' has already been in use.", new)
        nil
      else
        Thread.exclusive do
          v = @nicklist[old]
          @nicklist[new] = v
          v.nick = new
          @nicklist.delete(old)
        end
      end
    end # def change_nick

  end # class NickList

=begin rdoc

== PRUNE::ModeListBase

=end

  class ModeListBase
    include PRUNE::Debug

=begin rdoc

=== PRUNE::ModeListBase#new

=end

    def initialize
      @modemap = {}
      @namemap = {}
      @strmap = {}
      @mode = {}
      @methodmap = {}
    end # def initialize

    attr_reader :mode

=begin rdoc

=== PRUNE::ModeListBase#inspect

=end

    def inspect
      sprintf("#<%s:0x%x @mode:%s>",
              self.class, self.object_id, @mode.inspect)
    end # def inspect

=begin rdoc

=== PRUNE::ModeListBase#modename(mode)

=end

    def modename(mode)
      retval = []
      n = 0

      until mode == 0 do
        v = 1 << n
        if (mode & v) == v then
          retval.push(@namemap[v])
          mode ^= v
        end
        n += 1
      end

      retval
    end # def modename

=begin rdoc

=== PRUNE::ModeListBase#to_s

=end

    def to_s
      retval = []
      mode = "+"
      @mode.keys.sort.each do |k|
        if @mode[k].kind_of?(PRUNE::CiArray) || @mode[k].kind_of?(Array) then
          if defined?(@state) && !@state.nil? && !@methodmap[k].nil? && !@methodmap[k][3].nil? && !@methodmap[k][3].empty? then
            ret = @state.__send__(@methodmap[k][3])
            if ret.length > 0 then
              mode.concat(@strmap[k] * ret.length)
              retval.push(*ret)
            end
          else
            mode.concat(@strmap[k] * @mode[k].length)
            retval.push(*@mode[k])
          end
        elsif @mode[k].kind_of?(String) then
          mode.concat(@strmap[k])
          retval << @mode[k]
        elsif @mode[k].kind_of?(TrueClass) || @mode[k].kind_of?(FalseClass)
          mode.concat(@strmap[k]) if @mode[k] == true
        else
          mode.concat(@strmap[k])
          retval << @mode[k]
        end
      end
      retval.unshift(mode)

      retval.join(' ')
    end # def to_s

=begin rdoc

=== PRUNE::ModeListBase#clear

=end

    def clear
      @mode.clear
    end # def clear

=begin rdoc

=== PRUNE::ModeListBase#modeinfo(mode)

=end

    def modeinfo(mode)
      @modemap[mode]
    end # def modeinfo

  end # class ModeListBase

=begin rdoc

== PRUNE::ModeList

=end

  class ModeList < PRUNE::ModeListBase

    @@ModeListMap = %w(a,MODE_AWAY
                       i,MODE_INVISIBLE
                       w,MODE_WALLOPS
                       r,MODE_RESTRICTED
                       o,MODE_OPER
                       O,MODE_LOCAL_OPER
                       s,MODE_SERVER_NOTICE
                       )
    n = 1
    MODE_UNINITIALIZED = 1 << 0
    @@ModeListMap.each do |line|
      mode, info = line.split(',')
      module_eval <<-EOS, __FILE__, __LINE__+1
      #{info} = 1 << n
      EOS
      n += 1
    end

=begin rdoc

=== PRUNE::ModeList#new

=end

    def initialize
      super

      @@ModeListMap.each do |line|
        mode, info = line.split(',')
        @modemap[mode] = eval(info)
        @namemap[eval(info)] = info.sub(/^MODE_/, '')
        @strmap[eval(info)] = mode
      end
    end # def initialize

=begin rdoc

=== PRUNE::ModeList#availmode

=end

    class << self
      def availmode
        retval = ""

        @@ModeListMap.each do |line|
          mode, info = line.split(',')
          retval.concat(mode)
        end

        return retval
      end # def availmode
    end

=begin rdoc

=== PRUNE::ModeList#set_mode(mode, flag)

=end

    def set_mode(mode, flag)
      return nil if mode == 0

      @modemap.each_value do |x|
        if mode & x == x then
          if self.modename(x).empty? ||
            self.modename(x).include?(nil) then
            bug("unknown mode: %s", x)
          else
            @mode[x] = (flag == true)
          end
          mode &= ~x
        end
      end
      unless mode == 0 then
        bug("unknown mode: %s", mode)
        nil
      else
        flag == true
      end
    end # def set_mode

  end # class ModeList

=begin rdoc

== PRUNE::ChannelModeList

=end

  class ChannelModeList < PRUNE::ModeListBase

    @@ModeListMap = %w(i,MODE_INVITE|false
                       m,MODE_MODERATED|false
                       I,MODE_AUTO_INVITE|true,Array,,,validate_mask
                       o,MODE_OPER|true,Array,set_oper,opers
                       v,MODE_VOICE|true,Array,set_voice,voices
                       s,MODE_SECRET|false
                       k,MODE_KEY|true,String
                       l,MODE_LIMIT|true,0
                       b,MODE_BAN|true,Array,,,validate_mask
                       e,MODE_EXCEPT|true,Array,,,validate_mask
                       t,MODE_TOPIC|false
                       p,MODE_PRIVATE|false
                       a,MODE_ANONYMOUS|false
                       n,MODE_NO_EXTERNAL_MSGS|false
                       )
    n = 1
    MODE_UNINITIALIZED = 1 << 0
    @@ModeListMap.each do |line|
      l, p = line.split('|')
      mode, info = l.split(',')
      param, type, method, updater, validator = p.split(',')
      module_eval <<-EOS, __FILE__, __LINE__+1
      #{info} = 1 << n
      EOS
      n += 1
    end

=begin rdoc

=== PRUNE::ChannelModeList#new(state)

=end

    def initialize(state)
      raise ArgumentError, sprintf("Invalid argument: %s(%s)", state, state.class) unless state.kind_of?(PRUNE::ChannelState)
      super()

      @state = state

      @@ModeListMap.each do |line|
        l, p = line.split('|')
        mode, info = l.split(',')
        param, type, method, updater, validator = p.split(',')

        @modemap[mode] = eval(info)
        @namemap[eval(info)] = info.sub(/^MODE_/, '')
        @strmap[eval(info)] = mode
        v = 'nil'
        type = 'nil' if type.nil?
        if eval(type).public_methods.include?('new') then
          v = eval(type).new
        else
          v = eval(type)
        end
        @methodmap[eval(info)] = [eval(param), v, method, updater, validator]
        # ensure invoking external method to up to date the state.
        @mode[eval(info)] = [] if !updater.nil? && !updater.empty?
      end
    end # def initialize

=begin rdoc

=== PRUNE::ChannelModeList#availmode

=end

    class << self
      def availmode
        retval = ""

        @@ModeListMap.each do |line|
          mode, info = line.split(',')
          retval.concat(mode)
        end

        return retval
      end # def availmode
    end

=begin rdoc

=== PRUNE::ChannelModeList#modeinfo(mode)

=end

    def modeinfo(mode)
      retval = super(mode)
      return nil if retval.nil?

      return [retval, @methodmap[retval][0]]
    end # def modeinfo

=begin rdoc

=== PRUNE::ChannelModeList#set_mode(mode, flag, *args)

=end

    def set_mode(mode, flag, *args)
      return nil if mode == 0

      if self.modename(mode).empty? ||
          self.modename(mode).include?(nil) then
        bug("unknown mode: %s", mode)
        nil
      elsif self.modename(mode).length > 1 then
        warning("Unable to proceed the multiple modes.")
        nil
      else
        mthd = @methodmap[mode]
        if mthd[0] == true then
          if mthd[1].kind_of?(Array) then
            raise ArgumentError, sprintf("parameter must be a String, but %s", args[0].class) unless args[0].kind_of?(String)
            validated_value = nil
            if mthd[4].nil? then
              validated_value = args[0]
            else
              validated_value = __send__(mthd[4], args[0])
            end

            unless mthd[2].nil? || mthd[2].empty? then
              @state.__send__(mthd[2], validated_value, flag)
            end
            if mthd[3].nil? || mthd[3].empty? then
              @mode[mode] = PRUNE::CiArray.new unless @mode.has_key?(mode)
              if flag == true then
                @mode[mode].push(validated_value) unless @mode[mode].include?(validated_value)
              else
                @mode[mode].delete(validated_value) if @mode[mode].include?(validated_value)
              end
            else
              @mode[mode] = @state.__send__(mthd[3])
            end
          else
            raise ArgumentError, sprintf("parameter must be a %s, but %s", mthd[1].class, args[0].class) unless args[0].kind_of?(mthd[1].class)
            if flag == true then
              @mode[mode] = args[0]
            else
              @mode.delete(mode)
            end
          end
        else
          @mode[mode] = (flag == true)
        end
        flag
      end
    end # def set_mode

=begin rdoc

=== PRUNE::ChannelModeList#validate_mask(mask)

=end

    def validate_mask(mask)
      if mask =~ /\A(.*)!(.*)@(.*)\Z/ then
        mask
      elsif mask =~ /\A(.*)@(.*)\Z/ then
        sprintf("%s!*@%s", $1, $2)
      else
        sprintf("%s!*@*", mask)
      end
    end # def validate_mask

=begin rdoc

=== PRUNE::ChannelModeList#clear

=end

    def clear
      super
      # roll back the state for modes depends on the external instance.
      @modemap.map {|x,y| y}.each do |mode|
        mthd = @methodmap[mode]
        unless mthd[3].nil? || mthd[3].empty? then
          @mode[mode] = @state.__send__(mthd[3])
        end
      end
    end # def clear

  end # class ChannelModeList

=begin rdoc

== PRUNE::ChannelState

=end

  class ChannelState
    include PRUNE::Debug

=begin rdoc

=== PRUNE::ChannelState#new(name)

=end

    def initialize(name)
      raise ArgumentError, sprintf("Invalid channel name: %s", name) unless name =~ /#{PRUNE::PATTERN::CHANNEL}/
      @channelname = name
      @topic = nil
      @nicklist = PRUNE::NickList.new
      @mode = PRUNE::ChannelModeList.new(self)
    end # def initialize

    attr_accessor :topic

=begin rdoc

=== PRUNE::ChannelState#joined?(nick)

=end

    def joined?(nick)
      @nicklist.include?(nick)
    end # def joined?

=begin rdoc

=== PRUNE::ChannelState#join(nick)

=end

    def join(nick)
      if @nicklist.include?(nick) then
        bug("`%s' has already joined a channel `%s'", nick, @channelname)
      else
        @nicklist.add(PRUNE::TYPE::NickInfoStruct.new(nick))
      end
    end # def join

=begin rdoc

=== PRUNE::ChannelState#leave(nick)

=end

    def leave(nick)
      unless @nicklist.include?(nick) then
        bug("`%s' hasn't joined a channel `%s'", nick, @channelname)
      else
        @nicklist.delete(nick)
      end
    end # def leave

=begin rdoc

=== PRUNE::ChannelState#nicks

=end

    def nicks
      @nicklist.nicks
    end # def nicks

=begin rdoc

=== PRUNE::ChannelState#has_oper?(nick)

=end

    def has_oper?(nick)
      @nicklist.has_oper?(nick)
    end # def has_oper?

=begin rdoc

=== PRUNE::ChannelState#set_oper(nick, flag)

=end

    def set_oper(nick, flag)
      @nicklist.set_oper(nick, flag)
    end # def set_oper

=begin rdoc

=== PRUNE::ChannelState#clear_oper

=end

    def clear_oper
      @nicklist.clear_oper
    end # def clear_oper

=begin rdoc

=== PRUNE::ChannelState#opers

=end

    def opers
      @nicklist.opers
    end # def opers

=begin rdoc

=== PRUNE::ChannelState#has_voice?(nick)

=end

    def has_voice?(nick)
      @nicklist.has_voice?(nick)
    end # def has_voice?

=begin rdoc

=== PRUNE::ChannelState#set_voice(nick, flag)

=end

    def set_voice(nick, flag)
      @nicklist.set_voice(nick, flag)
    end # def set_voice

=begin rdoc

=== PRUNE::ChannelState#clear_voice

=end

    def clear_voice
      @nicklist.clear_voice
    end # def clear_voice

=begin rdoc

=== PRUNE::ChannelState#voices

=end

    def voices
      @nicklist.voices
    end # def voices

=begin rdoc

=== PRUNE::ChannelState#change_nick(old, new)

=end

    def change_nick(old, new)
      @nicklist.change_nick(old, new)
    end # def change_nick

=begin rdoc

=== PRUNE::ChannelState#has_mode?(mode)

=end

    def has_mode?(mode)
      if @mode.mode.include?(mode) then
        if @mode.mode[mode].kind_of?(FalseClass) then
          false
        else
          true
        end
      else
        false
      end
    end # def has_mode?

=begin rdoc

=== PRUNE::ChannelState#mode(mtype = nil)

=end

    def mode(mtype = nil)
      retval = []
      @mode.mode.each do |k, v|
        unless mtype.nil? then
          if k == mtype then
            retval = v
            break
          end
        else
          n = @mode.modename(k)[0]
          if v.kind_of?(Array) || v.kind_of?(PRUNE::CiArray) then
            retval << sprintf("%s:%s", n, v.join(',')) if v.length > 0
          elsif v.kind_of?(String) then
            retval << sprintf("%s:%s", n, v)
          elsif v.kind_of?(TrueClass) || v.kind_of?(FalseClass) then
            retval << n if v == true
          else
            retval << n
          end
        end
      end # @mode.mode.each

      retval
    end # def mode

=begin rdoc

=== PRUNE::ChannelState#mode_string

=end

    def mode_string
      @mode.to_s
    end # def mode_string

=begin rdoc

=== PRUNE::ChannelState#set_mode(*mode)

=end

    def set_mode(*mode)
      flag = !(mode[0] =~ Regexp.new("\\A\\+")).nil? # sigh
      modes = mode.shift.sub(/\A[+-]/, '').split(//)

      modes.each do |m|
        i, pf = @mode.modeinfo(m)
        if i.nil? then
          bug("unknown mode: %s for %s", m, @channelname)
        else
          @mode.set_mode(i, flag, (pf == true ? mode.shift : nil))
        end
      end
    end # def set_mode

=begin rdoc

=== PRUNE::ChannelState#clear_mode

=end

    def clear_mode
      @mode.clear
    end # def clear_mode

  end # class ChannelState

=begin rdoc

== PRUNE::IRCState

=end

  class IRCState
    include PRUNE::Debug

=begin rdoc

=== PRUNE::IRCState#new

=end

    def initialize(nick = nil, user = nil, name = nil, host = nil)
      raise ArgumentError, sprintf("Invalid nick name: %s", nick) if !nick.nil? && nick !~ /\A#{PRUNE::PATTERN::ENICKNAME}\Z/ #
      raise ArgumentError, sprintf("Invalid user name: %s", user) if !user.nil? && user !~ /\A#{PRUNE::PATTERN::USER}\Z/ #
      raise TypeError, sprintf("Can't convert %s into String", name.class) if !name.nil? && !name.kind_of?(String)
      raise TypeError, sprintf("Can't convert %s into String", host.class) if !host.nil? && !host.kind_of?(String)

      @nick = nick
      @user = user
      @name = name
      @host = host
      @state = {}
      @channel = PRUNE::CiHash.new
      @mode = PRUNE::ModeList.new
    end # def initialize

    attr_reader :nick, :user, :name, :host

=begin rdoc

=== PRUNE::IRCState#nick=(nick)

=end

    def nick=(nick)
      raise ArgumentError, sprintf("Invalid nick name: %s", nick) unless nick =~ /\A#{PRUNE::PATTERN::ENICKNAME}\Z/ #
      @nick = nick
    end # def nick=

=begin rdoc

=== PRUNE::IRCState#user=(value)

=end

    def user=(value)
      raise RuntimeError, "Can't change a user name." if loggedin?
      raise ArgumentError, sprintf("Invalid user name: %s", value) unless value =~ /\A#{PRUNE::PATTERN::USER}\Z/ #
      @user = value
    end # def user=

=begin rdoc

=== PRUNE::IRCState#name=(value)

=end

    def name=(value)
      raise RuntimeError, "Can't change a name." if loggedin?
      raise TypeError, sprintf("Can't convert %s into String", value.class) unless value.kind_of?(String)
      @name = value
    end # def name=

=begin rdoc

=== PRUNE::IRCState#host=(value)

=end

    def host=(value)
      raise RuntimeError, "Can't change a host name." if loggedin?
      raise TypeError, sprintf("Can't convert %s into String", value.class) unless value.kind_of?(String)
      @host = value
    end # def host=

=begin rdoc

=== PRUNE::IRCState#connected?

=end

    def connected?
      @state.has_key?(:Connected) && @state[:Connected]
    end # def connected?

=begin rdoc

=== PRUNE::IRCState#connected=(state)

=end

    def connected=(state)
      @state[:Connected] = state
      if state == false then
        self.loggedin = false if loggedin?
        self.authenticated = false if authenticated?
        @nick = nil
        @user = nil
        @name = nil
        @host = nil
      end
    end # def connected=

=begin rdoc

=== PRUNE::IRCState#listen?

=end

    def listen?
      @state.has_key?(:Listened) && @state[:Listened]
    end # def listen?

=begin rdoc

=== PRUNE::IRCState#listened=(state)

=end

    def listened=(state)
      @state[:Listened] = state
      if state == false then
        self.loggedin = false if loggedin?
        self.authenticated = false if authenticated?
        @nick = nil
        @user = nil
        @name = nil
        @host = nil
      end
    end # def listened=

=begin rdoc

=== PRUNE::IRCState#authenticated?

=end

    def authenticated?
      @state.has_key?(:Authenticated) && @state[:Authenticated]
    end # def authenticated?

=begin rdoc

=== PRUNE::IRCState#authenticated=(state)

=end

    def authenticated=(state)
      PRUNE.Fail(PRUNE::Error::NotYetConnected) unless connected? || listen?
      PRUNE.Fail(PRUNE::Error::AlreadyLoggedIn) if loggedin?
      @state[:Authenticated] = state
    end # def authenticated=

=begin rdoc

=== PRUNE::IRCState#loggedin?

=end

    def loggedin?
      @state.has_key?(:Loggedin) && @state[:Loggedin]
    end # def loggedin

=begin rdoc

=== PRUNE::IRCState#loggedin=(state)

=end

    def loggedin=(state)
      PRUNE.Fail(PRUNE::Error::NotYetConnected) if !(connected? || listen?) && state == true
      PRUNE.Fail(PRUNE::Error::UnableToLogOut) if connected? && loggedin? && state == false
      @state[:Loggedin] = state
    end # def loggedin=

=begin rdoc

=== PRUNE::IRCState#channel(channel)

=end

    def channel(channel)
      if @channel.has_key?(channel) then
        @channel[channel]
      else
        bug("No such channel `%s' available in %s", channel, self)
        nil
      end
    end # def channel

=begin rdoc

=== PRUNE::IRCState#channels

=end

    def channels
      @channel.origkeys
    end # def channels

=begin rdoc

=== PRUNE::IRCState#join(channel, nick)

=end

    def join(channel, nick)
      unless @channel.has_key?(channel) then
        @channel[channel] = PRUNE::ChannelState.new(channel)
      end
      @channel[channel].join(nick)
    end # def join

=begin rdoc

=== PRUNE::IRCState#joined?(channel, nick)

=end

    def joined?(channel, nick)
      unless @channel.has_key?(channel) then
        false
      else
        @channel[channel].joined?(nick)
      end
    end # def joined?

=begin rdoc

=== PRUNE::IRCState#leave(channel, nick)

=end

    def leave(channel, nick)
      unless @channel.has_key?(channel) then
        bug("No such channel `%s' available in %s", channel, self)
      else
        if @nick == nick then
          @channel.delete(channel)
        else
          @channel[channel].leave(nick)
        end
      end
    end # def leave

=begin rdoc

=== PRUNE::IRCState#quit(nick, suffix = nil)

=end

    def quit(nick, suffix = nil)
      if @nick.downcase == nick.downcase then
        @channel.clear
        @mode.clear
      else
        @channel.each do |ch, v|
          unless suffix.nil? then
            if ch =~ /#{suffix}\Z/ && v.joined?(nick) then
              v.leave(nick)
            end
          else
            if v.joined?(nick) then
              v.leave(nick)
            end
          end
        end
      end
    end # def quit(nick)

=begin rdoc

=== PRUNE::IRCState#has_mode?(mode)

=end

    def has_mode?(mode)
      if @mode.mode.include?(mode) then
        if @mode.mode[mode].kind_of?(FalseClass) then
          false
        else
          true
        end
      else
        false
      end
    end # def has_mode?

=begin rdoc

=== PRUNE::IRCState#mode(mtype = nil)

=end

    def mode(mtype = nil)
      m = 0
      @mode.mode.each do |k, v|
        if !mtype.nil? then
          if k == mtype then
            if v.kind_of?(TrueClass) || v.kind_of?(FalseClass) then
              m = k if v == true
            else
              m = k unless k.nil?
            end
            break
          end
        else
          m += k if v == true
        end
      end
      @mode.modename(m)
    end # def mode

=begin rdoc

=== PRUNE::IRCState#mode_string

=end

    def mode_string
      @mode.to_s
    end # def mode_string

=begin rdoc

=== PRUNE::IRCState#set_mode(*mode)

=end

    def set_mode(*mode)
      flag = !(mode[0] =~ Regexp.new("\\A\\+")).nil? # sigh
      modes = mode.shift.sub(/\A[+-]/, '').split(//)

      modes.each do |m|
        i = @mode.modeinfo(m)
        if i.nil? then
          bug("unknown nick mode: %s", m)
        else
          @mode.set_mode(i, flag)
        end
      end
    end # def set_mode

=begin rdoc

=== PRUNE::IRCState#clear_mode

=end

    def clear_mode
      @mode.clear
    end # def clear_mode

  end # class IRCState

=begin rdoc

=== PRUNE::ChannelStateReadOnly

=end

  class ChannelStateReadOnly
    @@ignore_methods = [:topic=,
                        :join, :leave,
                        :set_oper, :clear_oper,
                        :set_voice, :clear_voice,
                        :change_nick,
                        :set_mode, :clear_mode]

=begin rdoc

=== PRUNE::ChannelStateReadOnly#new(obj)

=end

    def initialize(obj)
      raise ArgumentError, sprintf("Invalid argument: %s(%s)", obj, obj.class) unless obj.kind_of?(PRUNE::ChannelState)

      @__obj__ = obj
    end # def initialize

    def method_missing(m, *args)
      if !@@ignore_methods.include?(m) && @__obj__.respond_to?(m) then
        @__obj__.__send__(m, *args)
      else
        raise NoMethodError, sprintf("undefined method `%s' for %s", m, self)
      end
    end # def method_missing

    def public_methods
      super.concat(@__obj__.__send__(:public_methods)).uniq.sort.reject {|m| @@ignore_methods.include?(m.to_sym)}
    end # def public_methods

  end # class ChannelStateReadOnly

=begin rdoc

=== PRUNE::IRCStateReadOnly

=end

  class IRCStateReadOnly
    @@ignore_methods = [:nick=,
                        :connected=, :listened=, :authenticated=, :loggedin=,
                        :join, :leave, :quit,
                        :set_mode, :clear_mode]

=begin rdoc

=== PRUNE::IRCStateReadOnly#new(obj)

=end

    def initialize(obj)
      raise ArgumentError, sprintf("Invalid argument: %s(%s)", obj, obj.class) unless obj.kind_of?(PRUNE::IRCState)

      @__obj__ = obj
    end # def initialize

=begin rdoc

=== PRUNE::IRCStateReadOnly#channel(channel)

=end

    def channel(channel)
      o = @__obj__.channel(channel)
      o.nil? ? nil : PRUNE::ChannelStateReadOnly.new(o)
    end # def channel

    def method_missing(m, *args)
      if !@@ignore_methods.include?(m) && @__obj__.respond_to?(m) then
        @__obj__.__send__(m, *args)
      else
        raise NoMethodError, sprintf("undefined method `%s' for %s", m, self)
      end
    end # def method_missing

    def public_methods
      super.concat(@__obj__.__send__(:public_methods)).uniq.sort.delete_if {|m| @@ignore_methods.include?(m.to_sym)}
    end # def public_methods

  end # class IRCStateReadOnly

end # module PRUNE
